using System;
using System.Reflection;

namespace UnitTesting.Fixturing
{
	abstract class ReflectionValueProviderBase : IValueProvider
	{
		readonly ArgumentProviderImp _argumentProviderImp;
		readonly IActivator _activator;
		readonly IValueProvider _valueProvider;
		readonly IFreezable _freezable;
		readonly object _fixtureOptions;

		protected ReflectionValueProviderBase(IArgumentProvider argumentProvider,
		                                      IActivator activator,
		                                      IValueProvider valueProvider,
		                                      IFreezable freezable,
		                                      object fixtureOptions)
		{
			_argumentProviderImp = new ArgumentProviderImp(GetConstructor, argumentProvider);
			_activator = activator;
			_valueProvider = valueProvider;
			_freezable = freezable;
			_fixtureOptions = fixtureOptions;
		}

		protected ReflectionValueProviderBase()
		{
		}

		public object Create(Type type)
		{
			Ensure.IsNotNull(() => type);
			var arguments = _argumentProviderImp.GetArguments(type);
			var activatorParams = CreateActivatorParams(_valueProvider, _freezable, type, arguments, _fixtureOptions);
			return arguments == null
			       	? NotResolved.Value
			       	: _activator.Create(activatorParams);
		}

		protected internal abstract ConstructorInfo GetConstructor(Type fromType);

		internal virtual ActivatorParams CreateActivatorParams(
			IValueProvider valueProvider,
			IFreezable freezable,
			Type type,
			object[] arguments,
			object fixtureOptions)
		{
			return new ActivatorParams
			{
				ValueProvider = valueProvider,
				Freezable = freezable,
				TargetType = type,
				Arguments = arguments,
				FixtureOptions = fixtureOptions
			};
		}

		class ArgumentProviderImp
		{
			readonly Func<Type, ConstructorInfo> _getConstructor;
			readonly IArgumentProvider _argumentProvider;

			internal ArgumentProviderImp(Func<Type, ConstructorInfo> getConstructor, IArgumentProvider argumentProvider)
			{
				_getConstructor = getConstructor;
				_argumentProvider = argumentProvider;
			}

			internal object[] GetArguments(Type type)
			{
				return type.IsInterface
				       	? CreateEmptyArguments()
				       	: CreateNotEmptyArguments(type);
			}

			static object[] CreateEmptyArguments()
			{
				return new object[0];
			}

			object[] CreateNotEmptyArguments(Type type)
			{
				var constructor = _getConstructor(type);
				if (constructor != null)
				{
					EnsureIsNotRecursive(constructor);
					return CreateArgumentsFor(constructor.GetParameters());
				}
				return null;
			}

			static void EnsureIsNotRecursive(ConstructorInfo constructor)
			{
				new RecursiveGuard(constructor).Do();
			}

			object[] CreateArgumentsFor(ParameterInfo[] parameters)
			{
				return _argumentProvider.GetArguments(parameters);
			}
		}
	}
}